package cn.gok.servlet;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;

import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @Author: chenfan
 * @Date: 2020/12/6 13:05
 * @Description: 自定义tomcat的主启动类
 */
public class Bootstrap {
    private int port;

    public int getPort(){
        return port;
    }

    public void setPort(int port){
        this.port = port;
    }


    //Mapper 中持续存放虚拟主机的map
    private Mapper mapper;


    /**
     * 加载server.xml 配置信息初始化tomcat
     * @throws Exception
     */
    private void loadServer() throws Exception {

        //加载server.xml 配置文件
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("source/conf/server.xml");
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(inputStream);
        Element root = document.getRootElement();

        Element connector = (Element)root.selectSingleNode("/server/service/connector");
        //解析出端口号
        port = Integer.parseInt(connector.attributeValue("port"));
        //解析出engine
        Node engine = connector.selectSingleNode("engine");

        //解析出engine 下的所有host ，host可以有多个
        List<Node> hosts = engine.selectNodes("host");

        mapper = new Mapper();

        for (int i = 0; i < hosts.size(); i++) {
            Element element  = (Element)hosts.get(i);
            String hostName = element.attributeValue("name");
            String appBasePath = element.attributeValue("appBase");

            Host host = new Host();
            //在mapper对象中建立起 虚拟站点名hostName 和  虚拟站点对host的关系
            mapper.getHostMap().put(hostName,host);

            //在host中构建 项目名 和 项目对象ServletContext的关系

            //也可能出现直接请求根目录appBasePath下资源的情况，此时项目名为空字符
            host.getContexts().put("", new ServletContext());


            //扫描appBase路径下的文件夹，每个文件夹都代表着一个项目
            File file = new File(appBasePath);
            File[] files = file.listFiles();
            for (int j = 0; j < files.length; j++) {
                if (files[j].isDirectory()) {
                    //文件夹的名字即是项目的名字
                    String appName = files[j].getName();
                    //创建对应的ServletContext对象
                    ServletContext servletContext = new ServletContext();
                    //设置项目的绝对路径值
                    servletContext.setContextAbsolutePath(appBasePath+"/"+appName);

                    //在host中构建起 项目名和 ServletContext的关系
                    host.getContexts().put(appName, servletContext);

                    //接着解析该项目下的web.xml文件， 在ServletContext中建立起 url 和 HttpServlet的映射关系
                    loadServlet(appBasePath,appName,servletContext);
                }
            }
        }
    }





    /**
     * 解析每个项目下的 web.xml ，初始化Servlet
     */
    private void loadServlet(String appBesePath , String appName ,ServletContext servletContext) throws Exception{

        /*
           构建加载此项目的类加载器:
                当前核心问题点在于，不能直接通过class.forname 去加载此项目中的类，
                因为用户部署的这个项目 和 我们当前的tomcat程序路径是不一样的，
                所以我们在加载这个项目中的类的时候，需要指定以这个项目路径为类的根路径，开始加载此项目中的类
         */
        ClassLoader classLoader = new WebClassLoader(appBesePath + "/" + appName);


        //获取此项目下的web.xml 文件的路径
        String webxmlPath = appBesePath +"/"+ appName + "/web.xml";

        InputStream inputStream = new FileInputStream(webxmlPath);
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(inputStream);
        Element root = document.getRootElement();

        List<Element> nodes = root.selectNodes("//servlet");
        for (int i = 0; i < nodes.size(); i++) {

            //解析每一个servlet元素
            Element element = nodes.get(i);

            Node servletNameElement = element.selectSingleNode("servlet-name");
            String servletNameValue = servletNameElement.getText();

            Node servletClassElement = element.selectSingleNode("servlet-class");
            String servletClassValue = servletClassElement.getText();

            //通过得到servlet-name的值， 找到对应的servlet-mapping
            Node servletMapping = root.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletNameValue + "']");
            String urlPattern = servletMapping.selectSingleNode("url-pattern").getText();

            //将得到的url 和 对应 httpServlet实例存入map中
            HttpServlet httpServlet =(HttpServlet)classLoader.loadClass(servletClassValue).getConstructor().newInstance();

            //在servletContext中构建每个url和 httpServlet的关系
            servletContext.getServletMap().put(urlPattern,httpServlet);
        }
    }


    public void start() throws Exception {
        loadServer();

        /*
          ThreadPoolExecutor(int corePoolSize,      线程池初始大小
                              int maximumPoolSize,   线程池最大值
                              long keepAliveTime,    空闲线程活跃时间
                              TimeUnit unit,          时间单位
                              BlockingQueue<Runnable> workQueue,  请求队列
                              ThreadFactory threadFactory,   线程工厂
                              RejectedExecutionHandler handler  拒绝处理策略
                          )
         */
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(10,
                50,
                100L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(50),
                new ThreadPoolExecutor.AbortPolicy());

        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("自定义tomcat启动，开始监听，port=" +port);

        while (true){
            Socket accept = serverSocket.accept();
            RequestProcessor processor = new RequestProcessor(accept, mapper);
            poolExecutor.execute(processor);
        }
    }


    public static void main(String[] args) {
        try {
            new Bootstrap().start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
